{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cac06555-9ce8-4f01-bbef-3f8407f4b54d",
   "metadata": {},
   "source": [
    "# Lab 3: Using MemGPT to build agents with memory \n",
    "This lab will go over: \n",
    "1. Creating an agent with MemGPT\n",
    "2. Understand MemGPT agent state (messages, memories, tools)\n",
    "3. Understanding core and archival memory\n",
    "4. Building agentic RAG with MemGPT "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aad3a8cc-d17a-4da1-b621-ecc93c9e2106",
   "metadata": {},
   "source": [
    "## Setup a Letta client \n",
    "Make sure you run `pip install letta_client` and start letta server `letta quickstart`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "067e007c-02f7-4d51-9c8a-651c7d5a6499",
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install letta_client\n",
    "!pip install letta\n",
    "!letta quickstart"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ccd43f2-164b-4d25-8465-894a3bb54c4b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from letta_client import CreateBlock, Letta, MessageCreate \n",
    "\n",
    "client = Letta(base_url=\"http://localhost:8283\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65bf0dc2-d1ac-4d4c-8674-f3156eeb611d",
   "metadata": {},
   "source": [
    "## Creating a simple agent with memory \n",
    "MemGPT allows you to create persistent LLM agents that have memory. By default, MemGPT saves all state related to agents in a database, so you can also re-load an existing agent with its prior state. We'll show you in this section how to create a MemGPT agent and to understand what memories it's storing. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe092474-6b91-4124-884d-484fc28b58e7",
   "metadata": {},
   "source": [
    "### Creating an agent "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a9d6228-a0f5-41e6-afd7-6a05260565dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "agent_name = \"simple_agent\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "62dcf31d-6f45-40f5-8373-61981f03da62",
   "metadata": {},
   "outputs": [],
   "source": [
    "agent_state = client.agents.create(\n",
    "    name=agent_name, \n",
    "    memory_blocks=[\n",
    "        CreateBlock(\n",
    "            label=\"human\",\n",
    "            value=\"My name is Sarah\",\n",
    "        ),\n",
    "        CreateBlock(\n",
    "            label=\"persona\",\n",
    "            value=\"You are a helpful assistant that loves emojis\",\n",
    "        ),\n",
    "    ]\n",
    "    model=\"openai/gpt-4o-mini\",\n",
    "    embedding=\"openai/text-embedding-3-small\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31c2d5f6-626a-4666-8d0b-462db0292a7d",
   "metadata": {},
   "outputs": [],
   "source": [
    "response = client.agents.messages.create(\n",
    "    agent_id=agent_state.id, \n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"user\", \n",
    "            content=\"hello!\", \n",
    "        ),\n",
    "    ]\n",
    ")\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20a5ccf4-addd-4bdb-be80-161f7925dae0",
   "metadata": {},
   "source": [
    "Note that MemGPT agents will generate a *reasoning_message* that explains its actions. You can use this monoloque to understand why agents are behaving as they are. \n",
    "\n",
    "Second, MemGPT agents also use tools to communicate, so messages are sent back by calling  a `send_message` tool. This makes it easy to allow agent to communicate over different mediums (e.g. text), and also allows the agent to distinguish betweeh that is and isn't send to the end user. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d33eca5-b8e8-4a8f-9440-85b45c37a777",
   "metadata": {},
   "source": [
    "### Understanding agent state \n",
    "MemGPT agents are *stateful* and are defined by: \n",
    "* The system prompt defining the agent's behavior (read-only)\n",
    "* The set of *tools* they have access to \n",
    "* Their memory (core, archival, & recall)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1cf7136-4060-441a-9d12-da851badf339",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(agent_state.system)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9e1c8c0-e98c-4952-b850-136b5b50a5ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "agent_state.tools"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae910ad9-afee-41f5-badd-a8dee5b2ad94",
   "metadata": {},
   "source": [
    "### Viewing an agent's memory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "478a0df6-3c87-4803-9133-8a54f9c00320",
   "metadata": {},
   "outputs": [],
   "source": [
    "memory = client.agents.core_memory.retrieve(agent_id=agent_state.id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff2c3736-5424-4883-8fe9-73a4f598a043",
   "metadata": {},
   "outputs": [],
   "source": [
    "memory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d6da43d6-847e-4a0a-9b92-cea2721e828a",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.context.retrieve(agent_id=agent_state.id)[\"num_archival_memory\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0399a1d6-a1f8-4796-a4c0-eb322512b0ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.context.retrieve(agent_id=agent_state.id)[\"num_recall_memory\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c7cce583-1f11-4f13-a6ed-52cc7f80e3c4",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.messages.list(agent_id=agent_state.id)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfd0a9ae-417e-4ba0-a562-ec59cb2bbf7d",
   "metadata": {},
   "source": [
    "## Understanding core memory \n",
    "Core memory is memory that is stored *in-context* - so every LLM call, core memory is included. What's unique about MemGPT is that this core memory is editable via tools by the agent itself. Lets see how the agent can adapt its memory to new information."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d259669c-5903-40b5-8758-93c36faa752f",
   "metadata": {},
   "source": [
    "### Memories about the human \n",
    "The `human` section of `ChatMemory` is used to remember information about the human in the conversation. As the agent learns new information about the human, it can update this part of memory to improve personalization. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "beb9b0ba-ed7c-4917-8ee5-21d201516086",
   "metadata": {},
   "outputs": [],
   "source": [
    "response = client.agents.messages.create(\n",
    "    agent_id=agent_state.id, \n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"user\", \n",
    "            content=\"My name is actually Bob\", \n",
    "        ),\n",
    "    ]\n",
    ")\n",
    "response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25f58968-e262-4268-86ef-1bed57e6bf33",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.core_memory.retrieve(agent_id=agent_state.id)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32692ca2-b731-43a6-84de-439a08a4c0d2",
   "metadata": {},
   "source": [
    "### Memories about the agent\n",
    "The agent also records information about itself and how it behaves in the `persona` section of memory. This is important for ensuring a consistent persona over time (e.g. not making inconsistent claims, such as liking ice cream one day and hating it another). Unlike the `system_prompt`, the `persona` is editable - this means that it can be used to incoporate feedback to learn and improve its persona over time. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f68851c5-5666-45fd-9d2f-037ea86bfcfa",
   "metadata": {},
   "outputs": [],
   "source": [
    "response = client.agents.messages.create(\n",
    "    agent_id=agent_state.id,\n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"user\", \n",
    "            content=\"In the future, never use emojis to communicate\", \n",
    "        ),\n",
    "    ]\n",
    ")\n",
    "response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2fc54336-d61f-446d-82ea-9dd93a011e51",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.core_memory.retrieve_block(agent_id=agent_state.id, block_label='persona')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "592f5d1c-cd2f-4314-973e-fcc481e6b460",
   "metadata": {},
   "source": [
    "## Understanding archival memory\n",
    "MemGPT agents store long term memories in *archival memory*, which persists data into an external database. This allows agents additional space to write information outside of its context window (e.g. with core memory), which is limited in size. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af63a013-6be3-4931-91b0-309ff2a4dc3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.archival_memory.list(agent_id=agent_state.id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bfa52984-fe7c-4d17-900a-70a376a460f9",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.context.retrieve(agent_id=agent_state.id)[\"num_archival_memory\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3ab0ae9-fc00-4447-8942-7dbed7a99222",
   "metadata": {},
   "source": [
    "Agents themselves can write to their archival memory when they learn information they think should be placed in long term storage. You can also directly suggest that the agent store information in archival. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c6556f76-8fcb-42ff-a6d0-981685ef071c",
   "metadata": {},
   "outputs": [],
   "source": [
    "response = client.agents.messages.create(\n",
    "    agent_id=agent_state.id, \n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"user\", \n",
    "            content=\"Save the information that 'bob loves cats' to archival\", \n",
    "        ),\n",
    "    ]\n",
    ")\n",
    "response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4429ffa-e27a-4714-a873-84f793c08535",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.archival_memory.list(agent_id=agent_state.id)[0].text"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae463e7c-0588-48ab-888c-734c783782bf",
   "metadata": {},
   "source": [
    "You can also directly insert into archival memory from the client. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f9d4194d-9ed5-40a1-b35d-a9aff3048000",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.archival_memory.create(\n",
    "    agent_id=agent_state.id, \n",
    "    text=\"Bob's loves boston terriers\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "338149f1-6671-4a0b-81d9-23d01dbe2e97",
   "metadata": {},
   "source": [
    "Now lets see how the agent uses its archival memory:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5908b10f-94db-4f5a-bb9a-1f08c74a2860",
   "metadata": {},
   "outputs": [],
   "source": [
    "response = client.agents.messages.create(\n",
    "    agent_id=agent_state.id, \n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"user\", \n",
    "            content=\"What animals do I like? Search archival.\", \n",
    "        ),\n",
    "    ]\n",
    ")\n",
    "response"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "letta-main",
   "language": "python",
   "name": "letta-main"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
